/*-
 * SMSFormatter.java
 *
 * $LastChangedBy:  $
 * $LastChangedRevision:  $
 * $LastChangedDate:  $
 * $HeadURL: $
 *
 * Copyright (c) 2008, Stein Gunnar Bakkeby
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Stein Gunnar Bakkeby nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.googlecode.attention.intention.tools;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.googlecode.attention.sms.pdu.Address;
import com.googlecode.attention.sms.pdu.CT;
import com.googlecode.attention.sms.pdu.ConcatenatedMessage;
import com.googlecode.attention.sms.pdu.DCS;
import com.googlecode.attention.sms.pdu.FCS;
import com.googlecode.attention.sms.pdu.PDUElement;
import com.googlecode.attention.sms.pdu.PDUElementText;
import com.googlecode.attention.sms.pdu.PI;
import com.googlecode.attention.sms.pdu.PID;
import com.googlecode.attention.sms.pdu.SCTS;
import com.googlecode.attention.sms.pdu.SMSCommand;
import com.googlecode.attention.sms.pdu.SMSDeliver;
import com.googlecode.attention.sms.pdu.SMSStatusReport;
import com.googlecode.attention.sms.pdu.SMSSubmit;
import com.googlecode.attention.sms.pdu.SMSSubmitReport;
import com.googlecode.attention.sms.pdu.TPDU;
import com.googlecode.attention.sms.pdu.UD;
import com.googlecode.attention.sms.pdu.UDH;
import com.googlecode.attention.util.Convert;

import static com.googlecode.attention.sms.pdu.PDUElement.*;

public class SMSFormatter {

  /*
   * %<key>
   * %<KEY> as verbose
   * #<key> as binary
   * 
   * %fo   - first octet
   *         %rp   - Reply Path indication (TP-RP)
   *         %rd   - Reject duplicates indication (TP-RD)
   *         %udhi - User data header indication (TP-UDHI)
   *         %sri  - Status report indication (TP-SRI)
   *         %srr  - Status report request (TP-SRR)
   *         %srq  - Status report qualifier (TP-SRQ)
   *         %mms  - More messages to send (TP-MMS)
   *         %vpf  - Validity period format (TP-VPF)
   *         %mti  - Message type indicator (TP-MTI)
   * %fcs  - error failure cause (TP-FCS)
   * %pi   - parameter indicator (TP-PI)
   * %pid  - protocol identifier (TP-PID)
   * %dcs  - data coding scheme (TP-DCS)
   * %mr   - message reference (TP-MR)
   * %mn   - message number (TP-MN)
   * %ct   - Command type (TP-CT)
   * %m    - message content (TP-UD, TP-CD)
   * %ud   - message content (TP-UD, TP-CD)
   * %cd   - message content (TP-UD, TP-CD)
   * %udl  - message length (TP-UDL, TP-CDL)
   * %a    - relevant address (TP-DA, TP-OA, TP-RA)
   * %toa  - type of address (TOA)
   *         %ton - type of number (TON)
   *         %npi - numbering plan information (NPI)
   * %ts   - Time stamp or validity period (TP-SCTS, TP-VP)
   * %scts - Time stamp or validity period (TP-SCTS, TP-VP)
   * %vp   - Time stamp or validity period (TP-SCTS, TP-VP)
   * %dt   - Discharge time (TP-DT)
   * %h    - User data header (TP-UDH)
   * %udh  - User data header (TP-UDH)
   * %udhl - User data header length (TP-UDHL)
   * %st   - Status (TP-ST)
   */
  
  /* First value is decimal indicator, the second binary indicator */ 
  final private static char[] flag = {'%', '#'};
  
  final static Map<String, PDUElement> elements
      = new HashMap<String, PDUElement>();
  {
    elements.put("fo", TP_FO);   
    elements.put("rp", TP_RP);
    elements.put("rd", TP_RD);
    elements.put("udhi", TP_UDHI);
    elements.put("sri", TP_SRI);
    elements.put("srr", TP_SRR);
    elements.put("srq", TP_SRQ);
    elements.put("mms", TP_MMS);
    elements.put("vpf", TP_VPF);
    elements.put("mti", TP_MTI);
    elements.put("fcs", TP_FCS);
    elements.put("pi", TP_PI);
    elements.put("pid", TP_PID);
    elements.put("dcs", TP_DCS);
    elements.put("mr", TP_MR);
    elements.put("mn", TP_MN);
    elements.put("ct", TP_CT);
    elements.put("m", TP_UD);
    elements.put("ud", TP_UD);
    elements.put("cd", TP_CD);
    elements.put("udl", TP_UDL);
    elements.put("a", TP_DA);
    elements.put("toa", TP_TOA);
    elements.put("ton", TP_TON);
    elements.put("npi", TP_NPI);
    elements.put("vp", TP_VP);
    elements.put("ts", TP_SCTS);
    elements.put("scts", TP_SCTS);
    elements.put("dt", TP_DT);
    elements.put("h", TP_UDH);
    elements.put("udh", TP_UDH);
    elements.put("udhl", TP_UDHL);
    elements.put("st", TP_ST);
  }

  private Phonebook phonebook;
  
  public String format(final String pattern,
                       final ConcatenatedMessage concat,
                       final String separator) {
    return format(pattern, null, concat, separator);
  }
  
  public String format(final String pattern,
                       final TPDU tpdu,
                       final String separator) {
    return format(pattern, tpdu, null, separator);
  }
  
  private String format(final String pattern,
                        TPDU tpdu,
                        final ConcatenatedMessage concat,
                        final String separator) {
    /*
     * ([^%#]+)?([%#])([A-Za-z]+)(?:\[([^]]+)\])?(?:\?\(([^)]+)\))?
     * 
     * TPDU: %T | %A | %PID?( -) %DCS - %FO - %v[yyyy/MM/dd]?( -) %m
     * 
     * start=36, end=53
     * Group(0) =  - %v[yyyy/MM/dd]
     * Group(1) =  - 
     * Group(2) = %
     * Group(3) = v
     * Group(4) = yyyy/MM/dd
     * Group(5) =  -
     */
    final StringBuilder sb = new StringBuilder();
    final Pattern regexPattern = Pattern.compile(
        "([^%#]+)?([%#])([A-Za-z]+)(?:\\[([^]]+)\\])?(?:\\?\\(([^)]+)\\))?");
    final Matcher matcher = regexPattern.matcher(pattern);
    int index = 0;
    
    final boolean hasConcat = concat != null;
    
    if (hasConcat) {
      final TPDU[] parts = concat.getParts();
      if (parts == null) {
        throw new IllegalStateException("Concatenated Message has no parts");
      }
      for (final TPDU part : parts) {
        if (part != null) {
          tpdu = part;
          break;
        }
      }
      if (tpdu == null) {
        throw new IllegalStateException("Concatenated Message has no parts");
      }
    }
    
    while (matcher.find()) {
      index = matcher.end();
      final String text = matcher.group(1);
      final char flag = matcher.group(2).charAt(0);
      final String key = matcher.group(3);
      final String patt = matcher.group(4);
      final String optionalText = matcher.group(5);
      final PDUElement element = elements.get(key.toLowerCase());
      if (element == null) {
        sb.append(matcher.group(0));
        continue;
      }
      if (text != null) sb.append(text);
      final int value = tpdu.get(element);
      
      final String info;
      if (key.equals(key.toUpperCase()) || value == -1) {
        if (hasConcat && (element == TP_UD || element == TP_CD)) {
          info = concat.getMessage();
        } else {
          info = getFullInfo(tpdu, element, separator, patt);
        }
      } else {
        if (flag == SMSFormatter.flag[0]) {
          info = Integer.toString(value);
        } else { // assume flag[1] (binary)
          info = Convert.intToBin(value, 8);
        }
      }
      
      if (info != null && info.length() != 0) {
        sb.append(info);
        if (optionalText != null) {
          sb.append(format(optionalText, tpdu, concat, separator));
        }
      }
    }
    sb.append(pattern.substring(index));
    return sb.toString();
  }
  
  public void setPhonebook(final Phonebook phonebook) {
    this.phonebook = phonebook;
  }
  
  private String getFullInfo(final TPDU tpdu,
                             final PDUElement element,
                             final String separator,
                             final String format) {
    final String text;
    switch (element) {
    case TP_FO:
      text = PDUElementText.getFOText(tpdu, separator);
      break;
      
    case TP_FCS:
      text = PDUElementText.getFCSText(new FCS(tpdu.get(element)));
      break;
      
    case TP_PI:
      text = PDUElementText.getPIText(new PI(tpdu.get(element)), separator);
      break;
    
    case TP_PID:
      text = PDUElementText.getPIDText(new PID(tpdu.get(element)));
      break;
    
    case TP_DCS:
      text = PDUElementText.getDCSText(new DCS(tpdu.get(element)), separator);
      break;
    
    case TP_MR:
      text = Integer.toString(tpdu.get(element));
      break;
    
    case TP_MN:
      text = Integer.toString(tpdu.get(element));
      break;
    
    case TP_CD:
    case TP_UD:
      if (tpdu instanceof SMSCommand) {
        text = PDUElementText.getCTText(new CT(tpdu.get(element)));        
      } else {
        text = tpdu.getMessage();        
      }
      break;
    
    case TP_DA:
    case TP_RA:
    case TP_OA:
      final Address address = tpdu.getAddress();
      if (address == null) {
        text = "";
      } else {
        text = (phonebook == null ? address.getNumber()
                                  : phonebook.findName(address));
      }
      break;
      
    case TP_TOA:
      text = PDUElementText.getTOAText(tpdu, separator);
      break;
      
    case TP_TON:
      text = PDUElementText.getTONText(tpdu);
      break;
      
    case TP_NPI:
      text = PDUElementText.getNPIText(tpdu);
      break;
    
    case TP_CT:
      text = PDUElementText.getCTText(tpdu);
      break;
    
    case TP_VP:
    case TP_SCTS:
      if (tpdu instanceof SMSSubmit) {
        text = PDUElementText.getVPText(tpdu);
      } else {
        final SCTS scts;
        switch (tpdu.getType()) {
        case TPDU.SMS_DELIVER:
          scts = ((SMSDeliver)tpdu).getServiceCentreTimeStamp();
          break;
          
        case TPDU.SMS_STATUS_REPORT:
          scts = ((SMSStatusReport)tpdu).getServiceCentreTimeStamp();
          break;
          
        case TPDU.SMS_SUBMIT_REPORT:
          scts = ((SMSSubmitReport)tpdu).getServiceCentreTimeStamp();
          break;
              
        default:
          scts = null;
          break;
        }
        
        if (format != null && scts != null) {
          scts.setSimpleDateFormatPattern(format);
        }
        text = (scts == null ? "" : scts.getTimeStamp());
      }
      break;
    
    case TP_DT:
      final SCTS dt;
      switch (tpdu.getType()) {
      case TPDU.SMS_STATUS_REPORT:
        dt = ((SMSStatusReport)tpdu).getDischargeTime();
        break;
            
      default:
        dt = null;
        break;
      }
      
      if (format != null) {
        dt.setSimpleDateFormatPattern(format);
      }
      text = (dt == null ? "" : dt.getTimeStamp());
      break;
      
    case TP_UDL:
      final UD ud = tpdu.getUserData();
      text = Integer.toString(ud.getUDL());
      break;
    
    case TP_MTI:
      text = PDUElementText.getMTIText(tpdu);
      break;
    
    case TP_MMS:
      text = PDUElementText.getMMSText(tpdu);
      break;
    
    case TP_RP:
      text = PDUElementText.getRPText(tpdu);
      break;
    
    case TP_UDHI:
      text = PDUElementText.getUDHIText(tpdu);
      break;
    
    case TP_UDH:
      final UD userData = tpdu.getUserData();
      final UDH udh = userData.getUDH();
      text = udh.toString();
      break;
    
    case TP_UDHL:
      text = Integer.toString(tpdu.getUserData().getUDH().getLength());
      break;
    
    case TP_SRI:
      text = PDUElementText.getSRIText(tpdu);
      break;
    
    case TP_SRR:
      text = PDUElementText.getSRRText(tpdu);
      break;
      
    case TP_SRQ:
      text = PDUElementText.getSRQText(tpdu);
      break;
    
    case TP_VPF:
      text = PDUElementText.getVPFText(tpdu);
      break;
    
    case TP_RD:
      text = PDUElementText.getRDText(tpdu);
      break;
    
    case TP_CDL:
      text = Integer.toString(tpdu.get(PDUElement.TP_CDL));
      break;
    
    case TP_ST:
      text = PDUElementText.getSTText(tpdu);
      break;
      
    default: // PDUElement
      text = "";
      break;
    }
    
    return text;
  }
}
